﻿using System.Net;
using System.Net.Http.Formatting;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
using static ChannelInfoList;

partial class Program
{
    static string _cameraIp = "172.20.60.147";
    //static string _cameraIp = "172.20.60.34";
    //static string _cameraIp = "172.21.100.10";
    static string _username = "admin";
    static string _password = "hk123456";
    static async Task GetAbsolute()
    {
        var url = $"/ISAPI/PTZCtrl/channels/1/absoluteEx";
        var result = await ISAPIGetAsync<PTZAbsoluteEx>(url);
    }

    static async Task PtzControl()
    {
        var ptzData = new PTZData { Pan = 0 };

        var url = "/ISAPI/PTZCtrl/channels/1/continuous";
        var status = await ISAPIPutAsync<PTZData, ResponseStatus>(url, ptzData);
    }
    static async Task QueryChannels()
    {
        var url = $"/ISAPI/AUXInfo/attributes/Channels";
        var result = await ISAPIGetAsync<ChannelInfoList>(url);
    }
    static async Task QueryDailyDistribution()
    {
        var trackId = 1501; // 替换为实际的轨道ID
        var month = "2025-05"; // 替换为实际的月份
        var url = $"/ISAPI/ContentMgmt/record/tracks/{trackId}/dailyDistribution";
        var data = new TrackDailyParam
        {
            Year = int.Parse(month[..4]),
            MonthOfYear = int.Parse(month[5..7])
        };
        var result = await ISAPIPostAsync<TrackDailyParam, TrackDailyDistribution>(url, data);
        var list = result.DayList.Where(m => m.Record);
        url = "/ISAPI/ContentMgmt/search";
        var data2 = new CMSearchDescription
        {
            SearchId = Guid.NewGuid(),
            TrackList = [trackId],
            TimeSpanList = [],
            MaxResults = 100,
            SearchResultPosition = 0,
            MetadataList = ["//recordType.meta.std-cgi.com"]
        };
        foreach (var item in list.Select(m => m.DayOfMonth))
        {
            data2.TimeSpanList = [new() { StartTime = $"{month}-{item}T00:00:00z", EndTime = $"{month}-{item}T23:59:59z" }];
            var result2 = await ISAPIPostAsync<CMSearchDescription, CMSearchResult>(url, data2);
            foreach (var item2 in result2.MatchList)
            {
                var uri = item2.MediaSegmentDescriptor.PlaybackURI;



                //string realm = "ac8ae5f3e19eb08735ff20c2";
                //string nonce = "74ed7b130";

                //string username = "admin";
                //string userpwd = "hk123456";
                //string cmd = "DESCRIBE";
                //string url = "rtsp://172.21.100.10/Streaming/tracks/101/?starttime=20250513T141522Z&endtime=20250513T145021Z&name=00000000821000000&size=1063223640";
                //string response = computeDigestResponse(username, userpwd, realm, cmd, nonce, url);

            }
        }
    }
    /// <summary>
    /// 创建HttpClient，并带上登录凭证和基地址
    /// </summary>
    /// <param name="ip"></param>
    /// <param name="username"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    static HttpClient CreateHttpClient(string ip, string username, string password)
    {
        //var credentials = new CredentialCache
        //{
        //    { new Uri("http://" + _cameraIp), "Digest", new NetworkCredential("admin", "hk123456") }
        //};
        var credentials = new NetworkCredential(username, password);
        var handler = new HttpClientHandler()
        {
            Credentials = credentials
        };
        var client = new HttpClient(handler)
        {
            BaseAddress = new Uri($"http://{ip}")
        };
        return client;
    }
    /// <summary>
    /// 根据xml中的命名空间，创建一个XmlMediaTypeFormatter，使其用于反序列化时能与xml命名空间匹配上
    /// </summary>
    /// <param name="xml"></param>
    /// <returns></returns>
    static XmlMediaTypeFormatter GetMediaTypeFormatter(string? xml = null)
    {
        if (xml == null)
        {
            return new MyMediaTypeFormatter();
        }
        var match = Regex.Match(xml, @"(?<=xmlns=""\s*?)[^""\s]+");
        string? xmlns = null;
        if (match != null && match.Success)
        {
            xmlns = match.Value;
        }
        return new MyMediaTypeFormatter(xmlns);
    }
    async static Task<TOutput> ISAPIGetAsync<TOutput>(string url) where TOutput : class
    {
        var client = CreateHttpClient(_cameraIp, _username, _password);
        HttpResponseMessage rm = await client.GetAsync(url);
        var resultXml = await rm.Content.ReadAsStringAsync();
        if (typeof(TOutput) == typeof(string))
        {
            return resultXml as TOutput ?? throw new InvalidOperationException();
        }
        var formatter = GetMediaTypeFormatter(resultXml);
        return await rm.Content.ReadAsAsync<TOutput>(new[] { formatter });
    }
    async static Task<TOutput> ISAPIPutAsync<TInput, TOutput>(string url, TInput data)
    {
        var client = CreateHttpClient(_cameraIp, _username, _password);
        var formatter = GetMediaTypeFormatter();
        var content = new ObjectContent<TInput>(data, formatter);
        await content.LoadIntoBufferAsync();
        //调试查看入参
        var inputXml = await content.ReadAsStringAsync();
        var rm = await client.PutAsync(url, content);
        var resultXml = await rm.Content.ReadAsStringAsync();
        formatter = GetMediaTypeFormatter(resultXml);
        return await rm.Content.ReadAsAsync<TOutput>([formatter]);
    }
    async static Task<TOutput> ISAPIPostAsync<TInput, TOutput>(string url, TInput data)
    {
        var client = CreateHttpClient(_cameraIp, _username, _password);
        var formatter = GetMediaTypeFormatter();
        var content = new ObjectContent<TInput>(data, formatter);
        await content.LoadIntoBufferAsync();
        //调试查看入参
        var inputXml = await content.ReadAsStringAsync();
        var rm = await client.PostAsync(url, content);
        var resultXml = await rm.Content.ReadAsStringAsync();
        formatter = GetMediaTypeFormatter(resultXml);
        return await rm.Content.ReadAsAsync<TOutput>([formatter]);
    }
    /// <summary>
    /// MD5　32位加密
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    static string md5(string str)
    {
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
        bytes = md5.ComputeHash(bytes);
        md5.Clear();

        string ret = "";
        for (int i = 0; i < bytes.Length; i++)
        {
            ret += Convert.ToString(bytes[i], 16).PadLeft(2, '0');//补0
        }
        return ret.PadLeft(32, '0'); //补0

    }


    static string computeDigestResponse(string username, string userpwd, string realm, string cmd, string nonce, string url)
    {
        // The "response" field is computed as:
        //    md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<url>))
        // or, if "fPasswordIsMD5" is True:
        //    md5(<password>:<nonce>:md5(<cmd>:<url>))
        string ha1 = md5(username + ":" + realm + ":" + userpwd);
        string ha2 = md5(cmd + ":" + url);
        string response = md5(ha1 + ":" + nonce + ":" + ha2);

        return response;
    }

}
public class PTZData
{
    [XmlElement("pan")]
    public int Pan { get; set; } = 0;

    [XmlElement("tilt")]
    public int Tilt { get; set; } = 0;

    [XmlElement("zoom")]
    public int Zoom { get; set; } = 0;
}
public class MyMediaTypeFormatter : XmlMediaTypeFormatter
{
    private readonly string? defaultNamespace;

    public MyMediaTypeFormatter(string? defaultNamespace = null)
    {
        UseXmlSerializer = true;
        this.defaultNamespace = defaultNamespace;
    }

    public override XmlSerializer CreateXmlSerializer(Type type)
    {
        return new XmlSerializer(type, defaultNamespace);
    }
    protected override XmlWriter CreateXmlWriter(Stream writeStream, HttpContent content)
    {
        XmlWriterSettings xmlWriterSettings = new XmlWriterSettings
        {
            Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true),
            CloseOutput = false,
            CheckCharacters = false
        };
        return XmlWriter.Create(writeStream, xmlWriterSettings);
    }
}

public class ResponseStatus
{
    [XmlElement("requestURL")]
    public required string RequestURL { get; set; }

    [XmlElement("statusCode")]
    public byte StatusCode { get; set; }

    [XmlElement("statusString")]
    public required string StatusString { get; set; }

    [XmlElement("subStatusCode")]
    public required string SubStatusCode { get; set; }
}

public partial class PTZAbsoluteEx
{

    [XmlElement("elevation")]
    public float Elevation { get; set; }

    [XmlElement("azimuth")]
    public float Azimuth { get; set; }

    [XmlElement("absoluteZoom")]
    public float AbsoluteZoom { get; set; }

    [XmlElement("focus")]
    public float Focus { get; set; }

    [XmlElement("focalLen")]
    public int FocalLen { get; set; }

    [XmlElement("horizontalSpeed")]
    public float HorizontalSpeed { get; set; }

    [XmlElement("verticalSpeed")]
    public float VerticalSpeed { get; set; }

}
[XmlRoot("trackDailyParam")]
public class TrackDailyParam
{
    [XmlElement("year")]
    public int Year { get; set; }
    [XmlElement("monthOfYear")]
    public int MonthOfYear { get; set; }
}
[XmlRoot("trackDailyDistribution")]
public class TrackDailyDistribution
{
    [XmlArray("dayList")]
    [XmlArrayItem("day", IsNullable = false)]
    public Day[] DayList { get; set; } = [];
    [XmlAttribute("version")]
    public decimal Version { get; set; }
    public class Day
    {
        [XmlElement("id")]
        public int Id { get; set; }
        [XmlElement("dayOfMonth")]
        public int DayOfMonth { get; set; }
        [XmlElement("record")]
        public bool Record { get; set; }
        [XmlElement("recordType")]
        public string RecordType { get; set; } = string.Empty;
    }
}


public class CMSearchDescription
{
    [XmlElement("searchID")]
    public Guid SearchId { get; set; }
    [XmlArray("trackList")]
    [XmlArrayItem("trackID")]
    public int[] TrackList { get; set; } = [];
    [XmlArray("timeSpanList")]
    [XmlArrayItem("timeSpan")]
    public List<SearchTimeSpan> TimeSpanList { get; set; } = [];
    [XmlElement("maxResults")]
    public int MaxResults { get; set; }
    [XmlElement("searchResultPostion")]
    public int SearchResultPosition { get; set; }
    [XmlArray("metadataList")]
    [XmlArrayItem("metadataDescriptor")]
    public string[] MetadataList { get; set; } = [];


    public class SearchTimeSpan
    {
        [XmlElement("startTime")]
        public required string StartTime { get; set; }
        [XmlElement("endTime")]
        public required string EndTime { get; set; }
    }
}
public class CMSearchResult
{
    [XmlElement("searchID")]
    public Guid SearchId { get; set; }
    [XmlElement("responseStatus")]
    public bool ResponseStatus { get; set; }
    [XmlElement("responseStatusStrg")]
    public required string ResponseStatusString { get; set; }
    [XmlElement("numOfMatches")]
    public int NumberOfMatches { get; set; }
    [XmlArray("matchList")]
    [XmlArrayItem("searchMatchItem")]
    public SearchMatchItem[] MatchList { get; set; } = [];
    [XmlArray("metadataMatches")]
    [XmlArrayItem("metadataDescriptor")]
    public string[] MetadataMatches { get; set; } = [];

    public class SearchMatchItem
    {
        [XmlElement("sourceID")]
        public required string SourceId { get; set; }
        [XmlElement("timeSpan")]
        public required CMSearchDescription.SearchTimeSpan TimeSapn { get; set; }
        [XmlElement("mediaSegmentDescriptor")]
        public required MediaSegmentDescriptorT MediaSegmentDescriptor { get; set; }
        public class MediaSegmentDescriptorT
        {
            [XmlElement("contentType")]
            public required string ContentType { get; set; }
            [XmlElement("codecType")]
            public required string CodecType { get; set; }
            [XmlElement("playbackURI")]
            public required string PlaybackURI { get; set; }
        }
    }
}


[XmlRoot("ChannelInfoList")]
public class ChannelInfoList: List<ChannelInfo>
{
    public partial class ChannelInfo
    {
        [XmlElement("id")]
        public byte Id { get; set; }
    }
}

